// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package output

import (
	"bufio"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/i18n"
	"chainmaker.org/chainmaker/smarteditor/session"
	"chainmaker.org/chainmaker/smarteditor/util"

	"github.com/88250/gulu"
)

// GoInstallHandler handles request of go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)

	uid, err := session.GetUidFromSession(r)
	if err != nil {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	locale := conf.GetUser(uid).Locale
	args, err := util.ReadArgs(r.Body)
	if err != nil {
		log.Errorf("read arg failed. err:%v", err)
		result.Code = -1
		return
	}
	sid := args["sid"].(string)
	filePath := args["file"].(string)

	if !session.CanAccess(uid, filePath) {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	curDir := filepath.Dir(filePath)
	cmd := exec.Command("go", "install")
	cmd.Dir = curDir
	SetCmdEnv(cmd, uid)
	log.Debugf("go install %s", curDir)

	stdout, err := cmd.StdoutPipe()
	if nil != err {
		log.Errorf("get stdout failed. err:%v", err)
		result.Code = -1
		return
	}

	stderr, err := cmd.StderrPipe()
	if nil != err {
		log.Errorf("get stderr failed. err:%v", err)
		result.Code = -1
		return
	}

	channelRet := map[string]interface{}{}
	if wsChannel, err := session.GetOutputWS(sid); err == nil {
		channelRet["output"] = "<span class='start-install'>" +
			i18n.Get(locale, "start-install").(string) + "</span>\n"
		channelRet["cmd"] = "start-install"

		err := wsChannel.WriteJSON(&channelRet)
		if nil != err {
			log.Errorf("write message to socket failed. err:%v", err)
			return
		}
		wsChannel.Refresh()
	}

	reader := bufio.NewReader(io.MultiReader(stdout, stderr))
	if err := cmd.Start(); nil != err {
		log.Error(err)
		result.Code = -1
		return
	}

	go runAsync(rand.Int(), cmd, uid, sid, curDir, locale, reader)
}

//runAsync run command async
func runAsync(runningId int, cmd *exec.Cmd, uid, sid, curDir, locale string, reader *bufio.Reader) {
	defer util.PanicLog()
	defer cmd.Wait()
	log.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]",
		uid, sid, runningId, curDir)

	// read all
	buf, _ := ioutil.ReadAll(reader)
	channelRet := map[string]interface{}{}
	channelRet["cmd"] = "go install"
	if 0 != len(buf) { // build error
		// build gutter lint

		errOut := string(buf)
		lines := strings.Split(errOut, "\n")

		if lines[0][0] == '#' {
			lines = lines[1:] // skip the first line
		}

		lints := []*Lint{}

		for _, line := range lines {
			if len(line) < 1 {
				continue
			}

			if line[0] == '\t' {
				// append to the last lint
				last := len(lints)
				msg := lints[last-1].Msg
				msg += line

				lints[last-1].Msg = msg

				continue
			}

			file := line[:strings.Index(line, ":")]
			left := line[strings.Index(line, ":")+1:]
			index := strings.Index(left, ":")
			lineNo := 0
			msg := left
			if index >= 0 {
				lineNo, _ = strconv.Atoi(left[:index])
				msg = left[index+2:]
			}

			lint := &Lint{
				File:     file,
				LineNo:   lineNo - 1,
				Severity: lintSeverityError,
				Msg:      msg,
			}

			lints = append(lints, lint)
		}

		channelRet["lints"] = lints

		channelRet["output"] = "<span class='install-error'>" +
			i18n.Get(locale, "install-error").(string) + "</span>\n" + errOut
	} else {
		channelRet["output"] = "<span class='install-succ'>" +
			i18n.Get(locale, "install-succ").(string) + "</span>\n"
	}

	if wsChannel, err := session.GetOutputWS(sid); err == nil {
		log.Debugf("User [%s, %s] 's running [go install]"+
			" [id=%d, dir=%s] has done", uid, sid, runningId, curDir)

		err := wsChannel.WriteJSON(&channelRet)
		if nil != err {
			log.Warn(err)
		}

		wsChannel.Refresh()
	}
}
